package controller;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class ClientController implements ClientInterface, Runnable {

    private Socket clientSocket;
    private ExecutorService threadsDataPool;
    private URL baseFolder;
    private BufferedReader inputClient;
    private PrintWriter outputClient;
    private boolean stop;

    ClientController(Socket clientSocket) {
        this.clientSocket = clientSocket;
        this.threadsDataPool = Executors.newCachedThreadPool();
        this.stop = false;
        this.baseFolder = getClass().getResource("/resources/ftpData/");
    }

    @Override
    public void run() {
        String inputCommand;
        String[] inputCommandSplit;
        URL currentFolder = baseFolder;
        Pattern pattern = Pattern.compile("\"([^\"]*)\""); //match quoted text.

        try {
            this.inputClient = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            this.outputClient = new PrintWriter(clientSocket.getOutputStream(), true);
            outputClient.println("\nConnected.");
            outputClient.println("\nCommands:\n");
            outputClient.println("LIST\t\tSends the current folder and subfolders structure on a JSON file.");
            outputClient.println("CWD \"dir\"\tChange the working directory.");
            outputClient.println("PUT filename\tUpload a file with the filename on the working directory.");
            outputClient.println("GET filename\tDownload the filename on the working directory.");
           
            outputClient.println("");

            while (!stop) {
                inputCommand = inputClient.readLine();
                inputCommandSplit = inputCommand.split(" ");

                if (inputCommandSplit[0].equals("LIST")) {
                    System.out.println("DOING LIST");

                    List<File> filesList = listFiles(currentFolder);
                    for (File file : filesList) {
                        System.out.println(file.getPath());
                    }
                } else if (inputCommandSplit[0].equals("CWD")) {
                    System.out.println("DOING CWD");
                    Matcher matcher = pattern.matcher(inputCommand);

                    String pathToMove;
                    if (!matcher.find()) {
                        pathToMove = inputCommandSplit[1].replace("\\", "/");
                    } else {
                        pathToMove = matcher.group().replace("\"", "");
                        pathToMove = pathToMove.replace("\\", "/");

                    }
                    String firstChar = pathToMove.substring(0, 1);
                    URL currentFolderAux;
                    if (firstChar.equals("/")) {
                        currentFolderAux = getClass().getResource("/resources/ftpData" + pathToMove + "/");
                    } else {
                        currentFolderAux = getClass().getResource("/resources/ftpData/" + currentFolder.getPath().replaceAll(baseFolder.getPath(), "") + pathToMove + "/");
                    }
                    if (currentFolderAux == null) {
                        try {
                            throw new Exception("Bad CWD Command");
                        } catch (Exception ex) {
                            System.err.println(ex.getMessage());
                        }
                    } else {
                        currentFolder = currentFolderAux;
                    }

                } else if (inputCommandSplit[0].equals("PUT")) {
                    ServerSocket dSocket = new ServerSocket(0);
                    String serverFile = "upload.txt";
                    outputClient.println("Ready to upload on port: \n" + dSocket.getLocalPort());
                    this.threadsDataPool.execute(new DataSocket(currentFolder, serverFile, dSocket, 1));

                } else if (inputCommandSplit[0].equals("GET")) {
                    ServerSocket dSocket = new ServerSocket(0);
                    String serverFile = "upload.txt";
                    outputClient.println("Ready to download on port: \n" + dSocket.getLocalPort());
                    this.threadsDataPool.execute(new DataSocket(currentFolder, serverFile, dSocket, 0));
                }

                //System.out.println(((baseFolder.toURI()).relativize(file.toURI())).getPath());
                //System.err.println("Resources " + getClass().getRes
                //System.out.println(((baseFolder.toURI()).relativize(file.toURIource("/resources/ftpData/test.txt"));
                //System.err.println("Resources " + getClass().getResource("/resources/ftpData/"));
            }
        } catch (IOException ex) {
            Logger.getLogger(ClientController.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    private List<File> listFiles(URL currentFolder) {
        List<File> list = new ArrayList<>();
        try {
            File currentFile = new File(currentFolder.getPath());
            for (File file : currentFile.listFiles()) {
                if (file.isDirectory()) {
                    list.add(file);
                    list.addAll(listFiles(file.toURI().toURL()));
                } else {
                    list.add(file);
                }
            }
            return list;
        } catch (MalformedURLException ex) {
            System.err.println("listFiles bad URL " + ex.getMessage());
        }
        return list;
    }

    private final class DataSocket implements Runnable {

        private URL dataFolder;
        private String fileName;
        private ServerSocket dSocket;
        private int mode; //1 upload 0 download

        public DataSocket(URL currentFolder, String fileName, ServerSocket dSocket, int mode) {

            this.dataFolder = currentFolder;
            this.fileName = fileName;
            this.dSocket = dSocket;
            this.mode = mode;

        }

        @Override
        public void run() {
            FileOutputStream outFile;
            try {
                Socket dataS = dSocket.accept();
                if (mode == 1) {
                    File upFile = new File(dataFolder.getPath() + fileName);
                    System.out.println(dataFolder.getPath() + fileName);
                    upFile.createNewFile();
                    InputStream inS = dataS.getInputStream();
                    outFile = new FileOutputStream(upFile);
                    byte[] buffer = new byte[512];
                    int count;
                    while ((count = inS.read(buffer)) > 0) {
                        inS.read(buffer);
                        outFile.write(buffer);
                    }
                    outFile.close();
                } else if (mode == 0) {
                    File downFile = new File(dataFolder.getPath() + fileName);

                    int count;
                    byte[] buffer = new byte[512];
                    OutputStream outS = dataS.getOutputStream();
                    BufferedInputStream in = new BufferedInputStream(new FileInputStream(downFile));
                    while ((count = in.read(buffer)) > 0) {
                        outS.write(buffer, 0, count);
                        outS.flush();
                    }
                }

                dataS.close();
                dSocket.close();

            } catch (IOException ex) {
                Logger.getLogger(ClientController.class.getName()).log(Level.SEVERE, null, ex);

            }
        }
    }
}
